用法与 useState 类似,区别是 如果 props 提供 value 值,将会用 value 值来初始化 state 状态。
用法
ts
const [innerChecked, setInnerChecked] = useMergedState<boolean>(false, {
value: checked,
defaultValue: defaultChecked,
});源码分析
ts
import * as React, { useState } from 'react';
/**
* Similar to `useState` but will use props value if provided.
*/
export default function useMergedState<T, R = T>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
onChange?: (value: T, prevValue: T) => void;
postState?: (value: T) => T;
},
): [R, (value: T, ignoreDestroy?: boolean) => void] {
const { defaultValue, value, onChange, postState } = option || {};
// 声明内部 value 值
const [innerValue, setInnerValue] = useState<T>(() => {
// value 存在,用 value 初始化 innerValue 的值
if (value !== undefined) {
return value;
}
// 同上
if (defaultValue !== undefined) {
return typeof defaultValue === 'function'
? (defaultValue as any)()
: defaultValue;
}
// defaultStateValue 优先级最低
return typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
});
let mergedValue = value !== undefined ? value : innerValue;
if (postState) {
// 数据处理
mergedValue = postState(mergedValue);
}
// 封装内部的 setState
const onChangeRef = React.useRef(onChange);
onChangeRef.current = onChange;
const triggerChange = React.useCallback(
(newValue: T, ignoreDestroy?: boolean) => {
setInnerValue(newValue, ignoreDestroy);
if (mergedValue !== newValue && onChangeRef.current) {
onChangeRef.current(newValue, mergedValue);
}
},
[mergedValue, onChangeRef],
);
// Effect of reset value to `undefined`
const prevValueRef = React.useRef(value);
React.useEffect(() => {
if (value === undefined && value !== prevValueRef.current) {
setInnerValue(value);
}
prevValueRef.current = value;
}, [value]);
return [mergedValue as unknown as R, triggerChange];
}纠错与补充
useMergedState不是简单的「受控 + 非受控」合并器,它的value、defaultValue、defaultStateValue优先级依次降低;如果你传入了value却忘记配合onChange,组件将永远保持受控值,看似「失效」实则设计如此。postState只在读阶段运行,不能在里面产生副作用;常见做法是把日期、布尔等做一次格式化,这样外部onChange拿到的是处理后的值。- 由于内部用
useState保存innerValue,在 React 18 并发模式下引用比较依旧安全;不过如果value可能短时间内频繁变动,请确保依赖它的副作用使用稳定引用,必要时结合useMemo。